/*
    作者：zx
    时间：2022.11.8
    描述：kernel/include/cpu/cpu.h 相关代码实现
*/

#include "cpu/cpu.h"
#include "os_cfg.h"
#include "comm/cpu_instr.h"
#include "cpu/irq.h"
#include "ipc/mutex.h"
#include "core/syscall.h"
static segment_desc_t   gdt_table[GDT_TABLE_SIZE];
static mutex_t mutex;

void segment_desc_set(int selector, uint32_t base, uint32_t limit, uint16_t attr)
{
    segment_desc_t * desc = gdt_table + (selector >> 3);

	// 如果界限比较长，将长度单位换成4KB
	if (limit > 0xfffff) {
		attr |= 0x8000;
		limit /= 0x1000;
	}
	desc->limit15_0 = limit & 0xffff;
	desc->base15_0 = base & 0xffff;
	desc->base23_16 = (base >> 16) & 0xff;
	desc->attr = attr | (((limit >> 16) & 0xf) << 8);
	desc->base31_24 = (base >> 24) & 0xff;
}

void init_gdt(void)
{
    for (int i = 0; i < GDT_TABLE_SIZE; i++)
    {
        segment_desc_set(i << 3, 0, 0, 0);
    }

    //数据段
    segment_desc_set(KERNEL_SELECTOR_DS, 0x00000000, 0xFFFFFFFF,
                     SEG_P_PRESENT | SEG_DPL0 | SEG_S_NORMAL | SEG_TYPE_DATA
                     | SEG_TYPE_RW | SEG_D | SEG_G);

    // 只能用非一致代码段，以便通过调用门更改当前任务的CPL执行关键的资源访问操作
    segment_desc_set(KERNEL_SELECTOR_CS, 0x00000000, 0xFFFFFFFF,
                     SEG_P_PRESENT | SEG_DPL0 | SEG_S_NORMAL | SEG_TYPE_CODE
                     | SEG_TYPE_RW | SEG_D | SEG_G);

    // 系统调用门，应用程序通过此门调用系统调用
    gate_desc_set((gate_desc_t *)(gdt_table + (SYSTEM_SELECTOR_SYSCALL >> 3)),
                    KERNEL_SELECTOR_CS,
                    (uint32_t)exception_handler_syscall,
                    GATE_P_PRESENT | GATE_DPL3 | GATE_TYPE_SYSCALL | SYSCALL_PARAM_COUNT);

    // 加载gdt
    lgdt((uint32_t)gdt_table, sizeof(gdt_table));
}

//                  调用门描述符       被调函数所在代码段描述符     偏移量        属性
void gate_desc_set(gate_desc_t * desc, uint16_t selector, uint32_t offset, uint16_t attr) {
	desc->offset15_0 = offset & 0xffff;
	desc->selector = selector;
	desc->attr = attr;
	desc->offset31_16 = (offset >> 16) & 0xffff;
}

// 从gdt表中找出空闲项，返回选择子
int gdt_alloc_desc()
{
    mutex_lock(&mutex);
    // 从第1项开始，第0项全为0 必须保留
    for(int i = 1;i < GDT_TABLE_SIZE;i++)
    {
        segment_desc_t * segment_desc = gdt_table + i;
        if(segment_desc->attr == 0)
        {
            mutex_unlock(&mutex);
            return (i << 3); 
        }
    }
    mutex_unlock(&mutex);
    return -1;
}

// 释放选择子
int gdt_free_selector(int selector)
{
    mutex_lock(&mutex);
    gdt_table[selector >> 3].attr = 0;
    mutex_unlock(&mutex);
}

void cpu_init(void)
{
    mutex_init(&mutex);
    init_gdt();
}

void switch_to_tss(int gdt_tss_selector)
{
    far_jump(gdt_tss_selector,0);
}